// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包文件路径以与目标操作系统定义的文件路径兼容的方式实现用于操作文件路径的实用程序。
// 
// 文件路径包使用正斜杠或反斜杠，
// 具体取决于操作系统。要处理URL 
// 等路径，无论操作
// 系统如何，都始终使用前斜杠，请参阅路径包。
package filepath

import (
	"errors"
	"io/fs"
	"os"
	"sort"
	"strings"
)

// lazybuf是一个延迟构造的路径缓冲区。
// 支持追加、读取之前追加的字节、
// 以及检索最终字符串。它不会分配缓冲区
// 来保存输出，直到该输出与s.
type lazybuf struct {
	path       string
	buf        []byte
	w          int
	volAndPath string
	volLen     int
}

func (b *lazybuf) index(i int) byte {
	if b.buf != nil {
		return b.buf[i]
	}
	return b.path[i]
}

func (b *lazybuf) append(c byte) {
	if b.buf == nil {
		if b.w < len(b.path) && b.path[b.w] == c {
			b.w++
			return
		}
		b.buf = make([]byte, len(b.path))
		copy(b.buf, b.path[:b.w])
	}
	b.buf[b.w] = c
	b.w++
}

func (b *lazybuf) string() string {
	if b.buf == nil {
		return b.volAndPath[:b.volLen+b.w]
	}
	return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
}

const (
	Separator     = os.PathSeparator
	ListSeparator = os.PathListSeparator
)

// Clean通过纯词法处理返回与路径
// 等效的最短路径名。它反复应用以下规则
// 直到无法进行进一步处理：
// 
// 1。用一个分离器元件替换多个分离器元件。
// 2。消除每一个。路径名元素（当前目录）。
// 3。消除每一个内在的。。path name元素（父目录）
// 以及非。。前面的元素。
// 4。排除以根路径开头的元素：
// 即替换“/”通过路径开头的“/”，
// 假设分隔符为“/”。
// 
// 仅当返回的路径表示根目录时，返回的路径才会以斜杠结尾，例如Unix上的“/”或Windows上的“C:\”。
// 
// 最后，斜杠的任何出现都将替换为分隔符。
// 
// 如果此过程的结果是空字符串，则Clean 
// 返回字符串“”。
// 
// 另请参见Rob Pike，`Plan 9中的词法文件名或
// 正确使用点，`
// https:
func Clean(path string) string {
	originalPath := path
	volLen := volumeNameLen(path)
	path = path[volLen:]
	if path == "" {
		if volLen > 1 && originalPath[1] != ':' {
			// 应该是UNC 
			return FromSlash(originalPath)
		}
		return originalPath + "."
	}
	rooted := os.IsPathSeparator(path[0])

	// 不变量：
	// 从路径读取；r是要处理的下一个字节的索引。
	// 写信给buf；w是要写入的下一个字节的索引。
	// dotdot是buf中的索引，其中。。必须停止，因为
	// 它是前导斜杠，或者它是前导../。。前缀
	n := len(path)
	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
	r, dotdot := 0, 0
	if rooted {
		out.append(Separator)
		r, dotdot = 1, 1
	}

	for r < n {
		switch {
		case os.IsPathSeparator(path[r]):
			// 空路径元素
			r++
		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
			// 。元素
			r++
		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
			// /。。元素：删除到最后一个分隔符
			r += 2
			switch {
			case out.w > dotdot:
				// 可以回溯
				out.w--
				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
					out.w--
				}
			case !rooted:
				// 不能回溯，但不是根，所以追加。。要素
				if out.w > 0 {
					out.append(Separator)
				}
				out.append('.')
				out.append('.')
				dotdot = out.w
			}
		default:
			// 实路径元素。
			// 如果需要添加斜杠
			if rooted && out.w != 1 || !rooted && out.w != 0 {
				out.append(Separator)
			}
			// 复制元素
			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
				out.append(path[r])
			}
		}
	}

	// 将空字符串转换为“”
	if out.w == 0 {
		out.append('.')
	}

	return FromSlash(out.string())
}

// ToSlash返回将路径中的每个分隔符
// 替换为斜杠（“/”）字符的结果。多个分隔符被
// 替换为多个斜杠。
func ToSlash(path string) string {
	if Separator == '/' {
		return path
	}
	return strings.ReplaceAll(path, string(Separator), "/")
}

// FromSlash返回用分隔符替换路径中每个斜杠（“/”字符）的结果。多个斜杠被多个分隔符取代。
func FromSlash(path string) string {
	if Separator == '/' {
		return path
	}
	return strings.ReplaceAll(path, "/", string(Separator))
}

// 拆分列表拆分由操作系统特定的ListSeparator连接的路径列表，
// 通常在PATH或GOPATH环境变量中找到。
// 与字符串不同。Split，SplitList在传递空
// 字符串时返回空切片。
func SplitList(path string) []string {
	return splitList(path)
}

// 拆分紧跟在最后一个分隔符后面的路径，
// 将其拆分为目录和文件名组件。
// 如果path中没有分隔符，Split将返回一个空目录
// 并将文件设置为path。
// 返回值的属性为path=dir+file。
func Split(path string) (dir, file string) {
	vol := VolumeName(path)
	i := len(path) - 1
	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
		i--
	}
	return path[:i+1], path[i+1:]
}

// Join将任意数量的path元素连接到一个路径中，
// 使用操作系统特定的分隔符将它们分隔开。空元素
// 被忽略。结果被清除了。但是，如果参数
// list为空或其所有元素为空，Join将返回一个空字符串
// 。
// 在Windows上，只有当第一个
// 非空元素是UNC路径时，结果才会是UNC路径。
func Join(elem ...string) string {
	return join(elem)
}

// Ext返回path使用的文件扩展名。
// 扩展名是路径的最后一个元素中从最后一个点开始的后缀
// ；如果有
// 没有点，则为空。
func Ext(path string) string {
	for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
		if path[i] == '.' {
			return path[i:]
		}
	}
	return ""
}

// EvalSymlinks在对任何符号
// 链接。
// 如果路径是相对的，则结果将相对于当前目录，除非其中一个组件是绝对符号链接。
// EvalSymlinks在结果上调用Clean。
func EvalSymlinks(path string) (string, error) {
	return evalSymlinks(path)
}

// Abs返回路径的绝对表示。wen jian defg
// 路径名不保证唯一。
// Abs对结果进行清除。
func Abs(path string) (string, error) {
	return abs(path)
}

func unixAbs(path string) (string, error) {
	if IsAbs(path) {
		return Clean(path), nil
	}
	wd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	return Join(wd, path), nil
}

// Rel返回一个相对路径，当
// 使用中间分隔符连接到basepath时，该相对路径在词汇上等同于targetPath。也就是说，
// Join（basepath，Rel（basepath，targetpath））等同于targetpath本身。
// 成功后，返回的路径将始终相对于basepath，
// 即使basepath和TargetPath不共享任何元素。
// 如果不能使targetPath相对于basepath，或者如果
// 知道当前工作目录需要计算它，则返回错误。
// Rel在结果上调用Clean。
func Rel(basepath, targpath string) (string, error) {
	baseVol := VolumeName(basepath)
	targVol := VolumeName(targpath)
	base := Clean(basepath)
	targ := Clean(targpath)
	if sameWord(targ, base) {
		return ".", nil
	}
	base = base[len(baseVol):]
	targ = targ[len(targVol):]
	if base == "." {
		base = ""
	} else if base == "" && volumeNameLen(baseVol) > 2 /* isUNC */ {
		// 将任何与`\\host\share`basepath匹配的目标路径视为绝对路径。
		base = string(Separator)
	}

	// 无法使用IsAbs-`a`和`a`在Windows中都是相对的。
	baseSlashed := len(base) > 0 && base[0] == Separator
	targSlashed := len(targ) > 0 && targ[0] == Separator
	if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
	}
	// 将基[b0:bi]和目标[t0:ti]定位在第一个不同的元素上。
	bl := len(base)
	tl := len(targ)
	var b0, bi, t0, ti int
	for {
		for bi < bl && base[bi] != Separator {
			bi++
		}
		for ti < tl && targ[ti] != Separator {
			ti++
		}
		if !sameWord(targ[t0:ti], base[b0:bi]) {
			break
		}
		if bi < bl {
			bi++
		}
		if ti < tl {
			ti++
		}
		b0 = bi
		t0 = ti
	}
	if base[b0:bi] == ".." {
		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
	}
	if b0 != bl {
		// 基本元素左。必须先上去再下去。
		seps := strings.Count(base[b0:bl], string(Separator))
		size := 2 + seps*3
		if tl != t0 {
			size += 1 + tl - t0
		}
		buf := make([]byte, size)
		n := copy(buf, "..")
		for i := 0; i < seps; i++ {
			buf[n] = Separator
			copy(buf[n+1:], "..")
			n += 3
		}
		if t0 != tl {
			buf[n] = Separator
			copy(buf[n+1:], targ[t0:])
		}
		return string(buf), nil
	}
	return targ[t0:], nil
}

// SkipDir用作WalkFuncs的返回值，表示将跳过调用中指定的目录。任何函数都不会将其作为错误返回
// 。
var SkipDir error = fs.SkipDir

// WalkFunc是Walk调用的函数类型，用于访问每个
// 文件或目录。
// 
// path参数包含作为前缀的Walk参数。
// 也就是说，如果使用根参数“dir”调用Walk，并在该目录中找到名为“a”的文件
// 则将使用
// 参数“dir/a”调用Walk函数。
// 
// 目录和文件用Join连接，这可能会清除
// 目录名：如果使用根参数“x/。/dir”
// 调用WACK，并在该目录中找到名为“a”的文件，则WACK函数将用参数“dir/a”，而不是“x/。/dir/a”调用。
// 
// info参数是fs。指定路径的文件信息。
// 
// 函数返回的错误结果控制如何继续行走。
// 如果函数返回特殊值SkipDir，Walk将跳过当前目录（如果info.IsDir（）为true，则路径为
// 父目录）。否则，如果函数返回非nil错误，
// Walk将完全停止并返回该错误。
// 
// err参数报告与路径相关的错误，表示Walk 
// 将不会进入该目录。该函数可以决定如何处理该错误；如前所述，返回错误将导致Walk停止遍历整个树。
// 
// Walk在两种情况下使用非nil err参数调用函数。首先，如果是操作系统。根目录或树中任何目录或文件
// 上的Lstat失败，Walk从操作系统调用path设置为该
// 目录或文件路径、info设置为nil、err设置为错误
// 的函数。伊斯塔特。
// 
// 第二，如果一个目录的Readdirnames方法失败，Walk调用
// 函数，将path设置为目录的path info，设置为
// fs。描述目录的FileInfo，并将err设置为
// Readdirnames中的错误。
type WalkFunc func(path string, info fs.FileInfo, err error) error

var lstat = os.Lstat // 用于测试

// walkDir递归下降路径，调用walkDirFn。
func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
	if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
		if err == SkipDir && d.IsDir() {
			// 已成功跳过目录。
			err = nil
		}
		return err
	}

	dirs, err := readDir(path)
	if err != nil {
		// 第二次调用，报告ReadDir错误。
		err = walkDirFn(path, d, err)
		if err != nil {
			return err
		}
	}

	for _, d1 := range dirs {
		path1 := Join(path, d1.Name())
		if err := walkDir(path1, d1, walkDirFn); err != nil {
			if err == SkipDir {
				break
			}
			return err
		}
	}
	return nil
}

// walk递归地向下移动路径，调用walkFn。
func walk(path string, info fs.FileInfo, walkFn WalkFunc) error {
	if !info.IsDir() {
		return walkFn(path, info, nil)
	}

	names, err := readDirNames(path)
	err1 := walkFn(path, info, err)
	// 如果出错！=nil walk不能进入这个目录。
	// err1！=nil表示walkFn希望walk跳过此目录或停止行走。
	// 因此，如果err和err1中的一个不是零，walk将返回。
	if err != nil || err1 != nil {
		// 调用方的行为由返回值控制，返回值由walkFn决定。walkFn可以忽略err并返回nil。
		// 如果walkFn返回SkipDir，它将由调用者处理。
		// 所以walk应该返回walkFn返回的任何东西。
		return err1
	}

	for _, name := range names {
		filename := Join(path, name)
		fileInfo, err := lstat(filename)
		if err != nil {
			if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
				return err
			}
		} else {
			err = walk(filename, fileInfo, walkFn)
			if err != nil {
				if !fileInfo.IsDir() || err != SkipDir {
					return err
				}
			}
		}
	}
	return nil
}

// WalkDir遍历根目录下的文件树，为树中的每个文件或
// 目录（包括根目录）调用fn。
// 
// 访问文件和目录时出现的所有错误都会被fn过滤：
// 请参阅fs。有关详细信息，请参阅WalkDirFunc文档。
// 
// 文件按词法顺序遍历，这使得输出具有确定性
// 但需要WalkDir将整个目录读入内存，然后再继续执行
// 遍历该目录。
// 
// WalkDir不遵循符号链接。
func WalkDir(root string, fn fs.WalkDirFunc) error {
	info, err := os.Lstat(root)
	if err != nil {
		err = fn(root, nil, err)
	} else {
		err = walkDir(root, &statDirEntry{info}, fn)
	}
	if err == SkipDir {
		return nil
	}
	return err
}

type statDirEntry struct {
	info fs.FileInfo
}

func (d *statDirEntry) Name() string               { return d.info.Name() }
func (d *statDirEntry) IsDir() bool                { return d.info.IsDir() }
func (d *statDirEntry) Type() fs.FileMode          { return d.info.Mode().Type() }
func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }

// Walk遍历根目录下的文件树，为树中的每个文件或
// 目录（包括根目录）调用fn。
// 
// 访问文件和目录时出现的所有错误都会被fn过滤：
// 有关详细信息，请参阅WalkFunc文档。
// 
// 这些文件是按词法顺序遍历的，这使得输出具有确定性
// 但在继续遍历该目录之前，需要遍历将整个目录读入内存。
// 
// Walk不遵循符号链接。
// 
// Walk的效率低于Go 1.16中引入的WalkDir，它避免了调用操作系统。在每个访问的文件或目录上进行Lstat。
func Walk(root string, fn WalkFunc) error {
	info, err := os.Lstat(root)
	if err != nil {
		err = fn(root, nil, err)
	} else {
		err = walk(root, info, fn)
	}
	if err == SkipDir {
		return nil
	}
	return err
}

// readDir读取以dirname命名的目录，并返回一个已排序的目录项列表。
func readDir(dirname string) ([]fs.DirEntry, error) {
	f, err := os.Open(dirname)
	if err != nil {
		return nil, err
	}
	dirs, err := f.ReadDir(-1)
	f.Close()
	if err != nil {
		return nil, err
	}
	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
	return dirs, nil
}

// readDirNames读取以dirname命名的目录，并返回
// 目录条目名称的排序列表。
func readDirNames(dirname string) ([]string, error) {
	f, err := os.Open(dirname)
	if err != nil {
		return nil, err
	}
	names, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return nil, err
	}
	sort.Strings(names)
	return names, nil
}

// Base返回路径的最后一个元素。
// 在提取最后一个元素之前，将删除尾随路径分隔符。
// 如果路径为空，则Base返回“”。
// 如果路径完全由分隔符组成，则Base返回单个分隔符。
func Base(path string) string {
	if path == "" {
		return "."
	}
	// 去除尾部斜线。
	for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
		path = path[0 : len(path)-1]
	}
	// 扔掉卷名
	path = path[len(VolumeName(path)):]
	// 查找最后一个元素
	i := len(path) - 1
	for i >= 0 && !os.IsPathSeparator(path[i]) {
		i--
	}
	if i >= 0 {
		path = path[i+1:]
	}
	// 如果现在为空，它只有斜杠。
	if path == "" {
		return string(Separator)
	}
	return path
}

// Dir返回路径的最后一个元素以外的所有元素，通常是路径的目录。
// 删除最后一个元素后，Dir在路径上调用Clean，并删除尾部的
// 斜杠。
// 如果路径为空，Dir返回“”。
// 如果路径完全由分隔符组成，则Dir返回单个分隔符。wen dang
func Dir(path string) string {
	vol := VolumeName(path)
	i := len(path) - 1
	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
		i--
	}
	dir := Clean(path[len(vol) : i+1])
	if dir == "." && len(vol) > 2 {
		// 必须是UNC 
		return vol
	}
	return vol + dir
}

// VolumeName返回前导卷名。
// 给定“C:\foo\bar”，它在Windows上返回“C:”。
// 给定“\\host\share\foo”，它返回“\\host\share”。
// 在其他平台上返回“”。
func VolumeName(path string) string {
	return path[:volumeNameLen(path)]
}
